﻿#region Copyright 2010 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#endregion

using System;
using System.IO;
using System.Reflection;
using CSharpTest.Net.Collections;

namespace NClassify.Generator.CodeWriters
{
    public class CsCodeWriter : CodeWriter
    {
        public const string Global = "global::";
        private readonly Assembly _generator;
        private readonly string _generateName, _generatorVersion;
        private bool hasPreamble;

        public CsCodeWriter(TextWriter writer) : base(writer, "{", "}")
        {
            _generator = Assembly.GetCallingAssembly();
            _generator = _generator ?? GetType().Assembly;
            _generateName = _generator.GetName().Name;
            _generatorVersion = _generator.GetName().Version.ToString(2);
        }

        public override GeneratorLanguage Language { get { return GeneratorLanguage.CSharp; } }

        public void WriteFilePreamble()
        {
            WriteLine("// Generated by {0}, Version={1}", _generateName, _generatorVersion);
            WriteLine("#pragma warning disable 0612, 1591, 3001, 3002, 3003, 3021");
            WriteLine("#region Designer generated code");
            hasPreamble = true;
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing && hasPreamble)
            {
                WriteLine("#endregion");
                hasPreamble = false;
            }

            base.Dispose(disposing);
        }

        public override string MakeString(string data)
        {
            return MakeCppString(data);
		}

        public void AddNamespaces(params string[] namespaces)
        {
            foreach (string ns in namespaces)
                WriteLine("using {0};", ns);
        }

        public override IDisposable WriteNamespace(string[] ns) 
        {
            if (ns == null || ns.Length == 0)
                return new DisposingList();//just some IDisposable... ignored.

            return WriteBlock("namespace {0}", CombineNames(".", ns));
        }

        public void WriteSummaryXml(string content, params object[] args)
        {
            if (String.IsNullOrEmpty(content))
                return;

            if (args != null && args.Length > 0)
                content = String.Format(content, args);
            string line;
            WriteLine("/// <summary>");
            using (StringReader sr = new StringReader(content))
                while (null != (line = sr.ReadLine()))
                    WriteLine("/// {0}", System.Web.HttpUtility.HtmlEncode(line));
            WriteLine("/// </summary>");
        }

        public override IDisposable CodeRegion(string format, params object[] args)
        {
            if(args.Length > 0)
                format = String.Format(format, args);
            WriteLine("#region " + format);
            return new ClosingBlock(this, x => WriteLine("#endregion"));
        }

        public void SetClsCompliant(bool isCompliant)
        {
            if (!isCompliant)
                WriteLine("[global::System.CLSCompliant(false)]");
        }

        public void WriteNonUserCode()
        {
            WriteLine("[" + Global + "System.Diagnostics.DebuggerNonUserCodeAttribute()]");
            WriteGenerated();
        }

        public void WriteGenerated()
        {
            WriteLine("[" + Global + "System.Runtime.CompilerServices.CompilerGeneratedAttribute()]");
            WriteLine("[" + Global + "System.CodeDom.Compiler.GeneratedCodeAttribute(\"{0}\", \"{1}\")]", _generateName, _generatorVersion);
        }

        public override string MakeConstant(FieldType type, string value)
        {
            try
            {
                const string zero = "0";
                switch (type)
                {
                    case FieldType.Int8:
                    case FieldType.Int16:
                        return String.Format("({0}){1}", GetTypeName(type), value ?? zero);
                    case FieldType.UInt8:
                    case FieldType.UInt16:
                        return String.Format("({0}){1}u", GetTypeName(type), value ?? zero);
                    case FieldType.Int32:
                        return (value ?? zero);
                    case FieldType.UInt32:
                        return (value ?? zero) + "U";
                    case FieldType.Int64:
                        return (value ?? zero) + "L";
                    case FieldType.UInt64:
                        return (value ?? zero) + "UL";
                    case FieldType.Float:
                        return (value ?? zero) + "F";
                    case FieldType.Double:
                        return (value ?? zero) + "D";
                    case FieldType.String:
                        return MakeString(value ?? String.Empty);

                    case FieldType.Boolean:
                        {

                            string r = String.IsNullOrEmpty(value)
                                           ? "false"
                                           : value == "1" || StringComparer.OrdinalIgnoreCase.Equals(value, "true")
                                                 ? "true"
                                                 : value == "0" ||
                                                   StringComparer.OrdinalIgnoreCase.Equals(value, "false")
                                                       ? "false"
                                                       : null;
                            if (r == null)
                                throw new ArgumentException("The value '" + value + "' is not a valid boolean.");
                            return r;
                        }

                    case FieldType.Bytes:
                        {
                            if (String.IsNullOrEmpty(value))
                                return "new byte[0]";
                            var test = Convert.FromBase64String(value);
                            return String.Format(Global + "System.Convert.FromBase64String({0})", MakeString(value));
                        }

                    case FieldType.Guid:
                        {
                            if (String.IsNullOrEmpty(value))
                                return Global + "System.Guid.Empty";
                            if (StringComparer.OrdinalIgnoreCase.Equals(value, "new()"))
                                return Global + "System.Guid.NewGuid()";
                            var test = new Guid(value);
                            return String.Format("new {0}System.Guid({1})", Global, MakeString(value));
                        }

                    case FieldType.DateTime:
                        {
                            if (String.IsNullOrEmpty(value))
                                return Global + "System.DateTime.MinValue";
                            if (StringComparer.OrdinalIgnoreCase.Equals(value, "now()"))
                                return Global + "System.DateTime.Now";

                            var test = DateTime.ParseExact(value, "u", null);
                            return String.Format("new {0}System.DateTime.ParseExact({1}, \"u\", null)", Global,
                                                 MakeString(value));
                        }

                    case FieldType.TimeSpan:
                        {
                            if (String.IsNullOrEmpty(value))
                                return Global + "System.TimeSpan.Zero";
                            var test = TimeSpan.Parse(value);
                            return String.Format("{0}System.TimeSpan.Parse({1})", Global, MakeString(value));
                        }

                    //case FieldType.Uri:
                    //    {
                    //        if (String.IsNullOrEmpty(value))
                    //            return "null";
                            
                    //        var test = new Uri(value, UriKind.Absolute);
                    //        return String.Format("new {0}System.Uri({1}, {0}System.UriKind.Absolute)", Global,
                    //                             MakeString(value));
                    //    }
                    default:
                        throw new ArgumentOutOfRangeException("value");
                }
            }
            catch(Exception ex)
            {
                throw new ArgumentException("Invalid argument for default of type " + type, ex);
            }
        }

        public override string GetTypeName(FieldType type)
        {
            switch (type)
            {
                case FieldType.Boolean: return "bool";
                case FieldType.Bytes: return "byte[]";
                case FieldType.Int8: return "sbyte";
                case FieldType.UInt8: return "byte";
                case FieldType.Int16: return "short";
                case FieldType.UInt16: return "ushort";
                case FieldType.Int32: return "int";
                case FieldType.UInt32: return "uint";
                case FieldType.Int64: return "long";
                case FieldType.UInt64: return "ulong";
                case FieldType.Float: return "float";
                case FieldType.Double: return "double";
                case FieldType.String: return "string";
                case FieldType.Guid: return Global + "System.Guid";
                case FieldType.DateTime: return Global + "System.DateTime";
                case FieldType.TimeSpan: return Global + "System.TimeSpan";
                //case FieldType.Uri: return Global + "System.Uri";
                default:
                    throw new ArgumentOutOfRangeException("type", "Invalid field type: " + type);
            }
        }

        public override IDisposable DeclareEnum(CodeItem info)
        {
            WriteSummaryXml(info.Description);
            WriteLineIf(info.Obsolete, "[" + Global + "System.Obsolete]");
            WriteLineIf(info.XmlName != null, "[" + Global + "System.Xml.Serialization.XmlType({0})]", MakeString(info.XmlName));
            WriteGenerated();
            return WriteBlock("{0}{1}enum {2}",
                info.Access == FieldAccess.Private ? null : info.Access.ToString().ToLower() + " ",
                info.HidesBase ? "new " : "",
                info.Name);
        }

        public override void WriteEnumValue(string name, uint value)
        {
            WriteSummaryXml("{0} = {1}", name, value);
            if(name != ToPascalCase(name))
                WriteLine("[" + Global + "System.Xml.Serialization.XmlEnum({0})]", MakeString(name));
            WriteLine("{0} = {1},", ToPascalCase(name), value);
        }

        public override IDisposable DeclareInterface(CodeItem info, string[] implements)
        {
            WriteSummaryXml(info.Description);
            WriteLineIf(info.Obsolete, "[" + Global + "System.Obsolete]");
            SetClsCompliant(info.ClsCompliant);
            WriteGenerated();
            return WriteBlock("{0}partial interface {1}{2}",
                info.Access == FieldAccess.Private ? null : info.Access.ToString().ToLower() + " ", 
                info.Name,
                implements == null || implements.Length == 0 ? null : " : " + String.Join(", ", implements));
        }

        public override IDisposable DeclareClass(CodeItem info, string[] implements)
        {
            WriteSummaryXml(info.Description);
            WriteLineIf(info.Obsolete, "[" + Global + "System.Obsolete]");
            WriteLineIf(info.XmlName != null, "[" + Global + "System.Xml.Serialization.XmlType({0})]", MakeString(info.XmlName));
            SetClsCompliant(info.ClsCompliant);
            WriteNonUserCode();
            return WriteBlock("{0}partial class {1}{2}",
                info.Access == FieldAccess.Private ? null : info.Access.ToString().ToLower() + " ",
                info.Name,
                implements == null || implements.Length == 0 ? null : " : " + String.Join(", ", implements));
        }

        public override IDisposable DeclareStruct(CodeItem info, string[] implements)
        {
            WriteSummaryXml(info.Description);
            WriteLineIf(info.Obsolete, "[" + Global + "System.Obsolete]");
            WriteLineIf(info.XmlName != null, "[" + Global + "System.Xml.Serialization.XmlType({0})]", MakeString(info.XmlName));
            SetClsCompliant(info.ClsCompliant);
            WriteNonUserCode();
            return WriteBlock("{0}partial struct {1}{2}",
                info.Access == FieldAccess.Private ? null : info.Access.ToString().ToLower() + " ",
                info.Name,
                implements == null || implements.Length == 0 ? null : " : " + String.Join(", ", implements));
        }

        public override IDisposable DeclareProperty(CodeItem info, string type)
        {
            WriteSummaryXml(info.Description);
            WriteLineIf(info.Obsolete, "[" + Global + "System.Obsolete]");
            SetClsCompliant(info.ClsCompliant || info.Access == FieldAccess.Private);

            WriteLineIf(info.DefaultValue != null && info.DefaultValue.IndexOf("global::") < 0, 
                "[" + Global + "System.ComponentModel.DefaultValueAttribute({0})]", info.DefaultValue);
            
            return WriteBlock("{0} {1} {2}",
                info.Access.ToString().ToLower(),
                type, info.Name
                );
        }

        public override void DeclareField(CodeItem info, string type, string defaultValue)
        {
            WriteLine("{0} {1} {2}{3};",
                info.Access.ToString().ToLower(),
                type,
                info.Name,
                defaultValue == null ? "" : " = " + defaultValue
                );
        }
    }
}
